/* 
 * File:   Conexion.cpp
 * Author: max
 * 
 * Created on October 18, 2012, 7:00 PM
 */

#include <iostream>
#include "errno.h"
#include "Conexion.h"

using namespace std;
using namespace TP::Comunicacion;

#define ERROR_CONEXION -1

Conexion::Conexion(const string& pPuerto, IManejadorConexionEvento* pManejador){
    
    this->puerto = pPuerto;
    this->manejadorConexionEvento = pManejador;
    this->sockfd = -1;
            
}

Conexion::~Conexion() {
}

void Conexion::Iniciar() throw (Excepcion&){
    
    try
    {
        // @todo: si el manjeador de nueva sesion no esta definido lanza una excepcion.
        if (NULL == this->manejadorConexionEvento)
            throw Excepcion ("No hay definido un manejador de evento de nueva sesion.");
        
        // Configuro la conexion.
        if (-1 == ((this->sockfd = socket(AF_INET, SOCK_STREAM, 0))))
        {
            string msg = "No se puede iniciar el socket. ";
            msg += strerror (errno);
            
            throw Excepcion (msg);
        }
        
        bzero((char *) &serv_addr, sizeof(serv_addr));
        this->serv_addr.sin_family = AF_INET;
        this->serv_addr.sin_addr.s_addr = INADDR_ANY;
        this->serv_addr.sin_port = htons(atoi(this->puerto.c_str()));
        
        if (-1 == bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))) 
        {
            string msg = "No se puede iniciar el puerto. ";
            msg += strerror (errno);
            throw Excepcion (msg);
        }
        
        if (-1 == listen(sockfd,5))
        {
            string msg = "Error en listen. ";
            msg += strerror(errno);
            throw Excepcion (msg);
        }
        
        this->conexionFinalizada = false;
        
        // Inicio la ejecucion.
        Thread::Iniciar();
    }
    catch (Excepcion& pEx)
    {
        string msg = "No se puede inicar la conexion (Puerto: ";
        msg += this->puerto;
        msg += ").";
        
        pEx.expandirMensaje(msg);
        throw;
    }
}

void Conexion::Finalizar() throw (Excepcion&){
    try
    {
        // Finalizo el socket de conexion.
        this->conexionFinalizada = true;
        
        if (-1 == shutdown(this->sockfd, SHUT_RDWR ))
        {
            this->conexionFinalizada = false;
            
            string msg = "Fallo al finalizar la conexion. ";
            msg += strerror (errno);
            throw Excepcion (msg);
        }
        
        Thread::Finalizar();
    }
    catch (Excepcion& pEx)
    {
        string msg = "No se puede finalizar la conexion (Puerto: " + this->puerto + ").";
        
        pEx.expandirMensaje(msg);
        throw;
    }
}

void Conexion::tarea (){
    
    this->errorConexion = false;
    
    while (!this->conexionFinalizada && !this->errorConexion)
    {
        /* Escucho una conexion entrante.  En caso de haber iniciado una conexion entrante
         * correctamente instancio la sesion y lo notifico al administrador de nuevas
         * sesiones.
         */
        struct sockaddr_in* cli_addr = (struct sockaddr_in*) new (struct sockaddr_in);
        socklen_t clilen = sizeof(cli_addr);
        int newsockfd = accept(this->sockfd,(struct sockaddr*) cli_addr, &clilen);

        if (ERROR_CONEXION != newsockfd) 
        {
            // Llego un nuevo cliente. Instancio la sesion y notifico.

            Sesion* sesion = new Sesion (newsockfd, cli_addr);
            this->manejadorConexionEvento->eventoNuevaSesion((IEnviante*) this, sesion);
        }
        else
        {
            if (cli_addr)
                delete cli_addr;
            
            if (!this->conexionFinalizada)
            {
                /* La conexion no fue finalizada por el servidor.
                 * El presente es un error de conexion.
                 */
                string msg = "ERROR_CONEXION";
                this->manejadorConexionEvento->eventoErrorConexionInexistente((IEnviante*) this, msg);
                this->errorConexion = true;
            }
        }
    }
}

